package me.gacl.websocket;

import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import java.util.stream.Stream;

/**
 * stream test
 * add by yaojinshan
 * since 20201127
 * 备注：
 *       1.流不存储数据，必须在流内操作数据
 *       2.顺序传递的函数，类型需要保持一致
 *
 * 前置：
 *      在使用stream之前，先理解一个概念：Optional 。
 *      Optional类是一个可以为null的容器对象。如果值存在则isPresent()方法会返回true，调用get()方法会返回该对象。更详细说明请见：菜鸟教程Java 8 Optional类
 */
@SuppressWarnings("all")
public class StreamTest {
    static List<Person> personList = new ArrayList<Person>();

    static {
        personList.add(new Person("Tom", 9500, 18,"male", "New York"));
        personList.add(new Person("Jack", 7000, 19,"male", "Washington"));
        personList.add(new Person("Lily", 7800, 20,"female", "Washington"));
        personList.add(new Person("Anni", 8200, 21,"male", "New York"));
        personList.add(new Person("Owen", 9500, 19,"male", "New York"));
        personList.add(new Person("Alisa", 7800, 21,"female", "New York"));
    }

    public static void main(String[] args) {
//        test1();
//        test2();
//        test3();
//        test4();
//        test5();
        test6();
//        test7();
//        test8();
//        test9();
//        test10();
//        test11();
//        test12();
//        test13();
//        test14();
    }


    /**
     * 流处理的创建
     */
    static void test1(){
        List<String> list = Arrays.asList("b", "c", "a");
        //1.直接调用Collection类的stream()方法
        Stream<String> stream = list.stream();
        //2.使用Arrays的泛型stream方法
        int array[] = {1,2,3,4,5};
        IntStream stream1 = Arrays.stream(array);
        //3.使用静态方法 of  iterate generate
        Stream<Integer> integerStream = Stream.of(1, 2, 3, 4);
        Stream<Integer> limit = Stream.iterate(0, (a) -> a + 3).limit(4);
        limit.forEach(System.out::println);
        //4. stream() 是顺序流，单线程顺序执行，
        stream.parallel();//将流转化成并行流，内部自动多线程处理，结果合并
    }

    /**
     * 遍历/匹配（foreach/find/match）
     */
    static void test2(){
        //1.forEach用法 其中参数 person -> {} 写法 使用的是java Lambda 写法 ，person 是集合在循环时，使用其代替当前元素（可以理解为变量名称）
        System.out.println("foreach用法：");
        personList.stream().forEach(person -> {
            System.out.println(person);
        });
        System.out.println("find用法：");
        //2.find 用法.其中 （格式：‘类名::方法名’，代表调用方法，例如System.out::println，如果写在方法体，则代表输出当前元素。
        Optional<Person> first = personList.stream().findFirst();
        //使用get()方法 获取容器中的元素
        System.out.println(first.get());
        // 一般搭配着filter一起使用
        // Optional<Person> first = personList.stream().filter(person -> person.getAge() > 19).findFirst();
        Optional<Person> any = personList.stream().findAny();
        System.out.println(any.get());
        //3.match用法
        System.out.println("match用法：");
        //存在一个 薪水大于7800的 即为true
        boolean a = personList.stream().anyMatch(person -> person.getSalary() > 7800);
        System.out.println("anyMatch:" + a);
        //存在全部 薪水大于7800的 即为true
        boolean b = personList.stream().allMatch(person -> person.getSalary() > 7800);
        System.out.println("allMatch:" + b);
        //不存在 薪水大于7800的 即为true
        boolean c = personList.stream().noneMatch(person -> person.getSalary() < 7000);
        System.out.println("noneMatch:" + c);
    }

    /**
     *  筛选（filter）
     */
    static void test3(){
        System.out.println("filter用法: 筛选出 年龄大于19的 并且 男性的人" );
        personList.stream().filter(person -> person.getAge() > 19 && person.getSex().equals("male")).forEach(System.out::println);
        //使用 collect函数 可以将数据进行转化给原型对象(下边有collect的详细讲解)
        List<Person> list = personList.stream().filter(person -> person.getAge() > 19 && person.getSex().equals("female")).collect(Collectors.toList());
        System.out.println(list);
    }

    /**
     * 聚合（max/min/count)
     */
    static void test4(){
        System.out.println("max用法: 筛选出 薪资最高值" );
        Optional<Person> max = personList.stream().max(Comparator.comparingInt(person -> person.getSalary()));
//        Optional<Person> max1 = personList.stream().max(Comparator.comparing(Person::getSalary));
        int maxSalary = max.get().getSalary();
        System.out.println(maxSalary);
        System.out.println("如果要是筛选出 薪资最高的人员列表，此处不能用max，需要使用filter" );
        personList.stream().filter(person -> person.getSalary() == maxSalary).forEach(System.out::println);

        //min 等同于max
        System.out.println("count用法: 筛选出 薪资最高值的人员个数" );
        System.out.println(personList.stream().filter(person -> person.getSalary() == maxSalary).count());
    }

    /**
     * 映射(map/flatMap)
     *
     * 映射，可以将一个流的元素按照一定的映射规则映射到另一个流中。分为 map 和 flatMap ：
     *
     * map：接收一个函数作为参数，该函数会被应用到每个元素上，并将其映射成一个新的元素。
     * flatMap：接收一个函数作为参数，将流中的每个值都换成另一个流，然后把所有流连接成一个流。
     */
    static void test5(){
        // map 方法 操作每一个元素后，得到的流元素不合并。
        System.out.println("map用法: 可以将一个流的元素按照一定的映射规则映射到另一个流中，底层所持有的的属性，是共有的，除非新new对象" );
        System.out.println("使用new对象的形式，建立新的流");
        List<Person> personListNew = personList.stream().map(person -> {
            Person personNew = new Person(person.getName(), person.getSalary(), person.getAge(), person.getSex(), person.getArea());
            personNew.setSalary(person.getSalary() + 10000);
            return personNew;
        }).collect(Collectors.toList());
        System.out.println("源数据薪水：" + personList.get(0).getName() + "-->" + personList.get(0).getSalary());
        System.out.println("new对象之后的薪水：" + personListNew.get(0).getName() + "-->" + personListNew.get(0).getSalary());

        System.out.println("使用原对象的形式，建立新的流");
        List<Person> personListNew2 = personList.stream().map(person -> {
            person.setSalary(person.getSalary() + 10000);
            return person;
        }).collect(Collectors.toList());
        System.out.println("源数据薪水：" + personList.get(0).getName() + "-->" + personList.get(0).getSalary());
        System.out.println("调整源数据的薪水：" + personListNew2.get(0).getName() + "-->" + personListNew2.get(0).getSalary());
        personListNew2.get(0).setSalary(100000);
        System.out.println("源数据的对象 薪水：" + personList.get(0).getName() + "-->" + personList.get(0).getSalary());
        System.out.println("改变新流的对象 薪水：" + personListNew2.get(0).getName() + "-->" + personListNew2.get(0).getSalary());

        String[] a =  new String[]{"aa,bb,cc","aaa,bbb,ccc"};
        // map 方法 操作每一个元素后，得到的数据，不合并输出。
        System.out.println(Arrays.stream(a).map(x -> {
            String[] split = x.split(",");
            return split;
        }).collect(Collectors.toList()));
        //flatMap : 对每一个元素进行处理，返回类型必须是stream，将所有的stream合并输出。
        // 用法 1.将多个流 合并成一个流,参看 test6() 方法,
        // 用法 2.处理数组中，每一个元素
        System.out.println(Arrays.stream(a).flatMap(x -> {
            String[] split = x.split(",");
            return Arrays.stream(split);
        }).collect(Collectors.toList()));

    }

    /**
     * 两个集合的 交集 并集 差集 示例
     */
    static void test6(){
        List<Person> l1 = new ArrayList<>();
        l1.add(personList.get(0));
        l1.add(personList.get(1));
        l1.add(personList.get(2));
        List<Person> l2 = new ArrayList<>();
        l2.add(personList.get(1));
        l2.add(personList.get(2));
        l2.add(personList.get(3));
        l1.stream().filter(l2::contains).collect(Collectors.toList()).forEach(System.out::println); //打印两者的交集
        l1.stream().filter(x -> {
            if(l2.contains(x)){
                return true;
            }else{
                return false;
            }
        }).collect(Collectors.toList()).forEach(System.out::println);

        System.out.println("并集：" + Stream.of(l1, l2).flatMap(Collection::stream).distinct().collect(Collectors.toList()));//打印两者的并集
        System.out.println(Stream.of(l1, l2).flatMap(people -> people.stream()).distinct().collect(Collectors.toList()));
        System.out.println(Stream.concat(l1.stream(), l2.stream()).distinct().collect(Collectors.toList()));

        System.out.println("差集：" + l1.stream().filter(x -> !l2.contains(x)).collect(Collectors.toList()));//打印两者的差集
        System.out.println("差集：" + l1.stream().filter(x -> {
            if(!l2.contains(x)){
                return true;
            }else {
                return false;
            }
        }).collect(Collectors.toList()));
    }

    /**
     * 归约(reduce)：也称缩减，顾名思义，是把一个流缩减成一个值，能实现对集合求和、求乘积和求最值操作
     *  备注：该函数支持接受2个函数参数，顺序执行
     */
    static void test7(){
        List<Integer> list = Arrays.asList(1, 3, 2, 8, 11, 4);
        // 求和方式1   identity:本体
        Optional<Integer> sum0 = list.stream().reduce((x, y) -> {
            System.out.println("x:" + x);
            System.out.println("y:" + y);
            return x + y;
        });
        Optional<Integer> sum1 = list.stream().reduce((x, y) -> x + y);
        System.out.println(list.stream().reduce(Integer::sum).get());
        // 求和方式2
        Optional<Integer> sum2 = list.stream().reduce(Integer::sum);
        // 求和方式3
        Integer sum3 = list.stream().reduce(0, Integer::sum);

        // 求乘积
        Integer multiply = list.stream().reduce((x, y) -> x * y).get();

        // 求最大值方式1
        Optional<Integer> maxSalary1 = list.stream().reduce((x, y) -> x > y ? x : y);
        // 求最大值写法2
        Integer maxSalary2 = list.stream().reduce(0, Integer::max);

        System.out.println("list求和：" + sum0.get() + "," + sum1.get() + "," + sum2.get() + "," + sum3);
        System.out.println("list求积：" + multiply);
        System.out.println("list求最大值：" + maxSalary1.get() + "," + maxSalary2);

        //使用reduce 求出最大的工资
        //思路：先使用map，将对象的属性给转成新的stream,然后再计算新stream的最大值
        // 求最高工资方式1：
        System.out.println("最大工资算法1：" + personList.stream().map(person -> person.getSalary()).reduce(Integer::max).get());
        // 求最高工资方式2：
        System.out.println("最大工资算法2：" + personList.stream().reduce(0, (max, p) -> max > p.getSalary() ? max : p.getSalary(),
                Integer::max));
        // 求最高工资方式3：
        System.out.println("最大工资算法3：" + personList.stream().reduce(0, (max, p) -> max > p.getSalary() ? max : p.getSalary(),
                (max1, max2) -> max1 > max2 ? max1 : max2));
        // 求最高工资方式4：
        System.out.println("最大工资算法4：" + personList.stream().map(person -> person.getSalary()).max(Integer::compareTo).get());
    }

    /**
     * 收集(collect) - 1 归集(toList/toSet/toMap),
     *    流不存储数据，那么在流中的数据完成处理后，需要将流中的数据重新归集到新的集合里。toList、toSet和toMap比较常用，
     *     另外还有toCollection、toConcurrentMap等复杂一些的用法。
     */
    static void test8(){
        System.out.println("结果集转换为：list " + personList.stream().filter(person -> person.getSalary() >= 8000).collect(Collectors.toList()));
        System.out.println("结果集转换为：set  " + personList.stream().filter(person -> person.getSalary() >= 8000).collect(Collectors.toSet()));
        System.out.println("结果集转换为：map  " + personList.stream().filter(person -> person.getSalary() >= 8000).collect(Collectors.toMap(Person::getName, Person::getSalary)));
//        System.out.println("结果集转换为：map  " + personList.stream().filter(person -> person.getSalary() >= 8000).collect(Collectors.toMap(Person::getName, person -> person)));
    }
    /**
     * 收集(collect) - 2 数据统计的静态方法：
     *      计数：count
     *      平均值：averagingInt、averagingLong、averagingDouble
     *      最值：maxBy、minBy
     *      求和：summingInt、summingLong、summingDouble
     *      统计以上所有：summarizingInt、summarizingLong、summarizingDouble
     */
    static void test9(){
        //案例：统计工资不小于8000的 员工人数、平均工资、工资总额、最高工资。
        System.out.println("员工人数：" + personList.stream().filter(person -> person.getSalary() >= 8000).collect(Collectors.counting()));
        System.out.println("平均工资：" + personList.stream().filter(person -> person.getSalary() >= 8000).collect(Collectors.averagingDouble(Person::getSalary)));
        System.out.println("工资总额：" + personList.stream().filter(person -> person.getSalary() >= 8000).collect(Collectors.summingDouble(Person::getSalary)));
        System.out.println("最高工资：" + personList.stream().filter(person -> person.getSalary() >= 8000).collect(Collectors.maxBy(Comparator.comparingDouble(Person::getSalary))).get());
        System.out.println("所有指标：" + personList.stream().filter(person -> person.getSalary() >= 8000).collect(Collectors.summarizingDouble(Person::getSalary)));
    }

    /**
     * 收集(collect) - 3 分组(partitioningBy/groupingBy)
     *  partitioningBy:分区，同一级别的key，只存在true,false
     *  groupingBy：分组，按照某个条件进行分组
     *  注意：
     *      1.两个函数可以交叉使用
     *      2.如果条件是固定值则使用“groupingBy”更好，如果条件不固定则使用“partitioningBy”
     */
    static void test10(){
        //先使用薪水大于8000的条件分区，再使用性别条件分区
        System.out.println("partitioningBy 分区 : " + personList.stream().collect(Collectors.partitioningBy(person -> person.getSalary() > 8000, Collectors.partitioningBy(person -> person.getAge() > 20))));
        //先试用性别分组，再使用年龄分组
        System.out.println("groupingBy 分区 : " + personList.stream().collect(Collectors.groupingBy(Person::getSex, Collectors.groupingBy(person -> person.getAge()))));
    }

    /**
     * 收集(collect) - 4 接合(joining)
     *  joining可以将stream中的元素用特定的连接符（没有的话，则直接连接）连接成一个字符串
     *  备注：
     *      使用joining方法时，前边的元素必须是基础数据类型？(目前表象是这样的)
     */
    static void test11(){
        System.out.println("元素连接：" + personList.stream().map(Person::getName).collect(Collectors.joining(",")));
        System.out.println("拼接后的字符串：" + Arrays.asList("A", "B", "C").stream().collect(Collectors.joining("-")));
    }

    /**
     * 收集(collect) - 5 归约(reducing)
     *      Collectors类提供的reducing方法，相比于stream本身的reduce方法，增加了对自定义归约的支持
     */
    static void test12(){
        System.out.println("在Collectors中使用自定义规约条件：需要纳税的薪资之和 " +
                personList.stream().collect(Collectors.reducing(0, person -> person.getSalary(), (x, y) -> (x + y - 5000))));
    }

    /**
     * 排序 (sorted)
     *     注意：
     *          1.默认都是升序排列
     *          2.reversed()是逆序排列
     *          3.thenComparing 代表次一级排序，可以支持多个
     */
    static void test13(){
        // 按工资升序排序（自然排序）
        List<String> newList = personList.stream().sorted(Comparator.comparing(Person::getSalary)).map(Person::getName)
                .collect(Collectors.toList());
        // 按工资倒序排序
        List<String> newList2 = personList.stream().sorted(Comparator.comparing(Person::getSalary).reversed())
                .map(Person::getName).collect(Collectors.toList());
        // 先按工资再按年龄升序排序
        List<String> newList3 = personList.stream()
                .sorted(Comparator.comparing(Person::getSalary).thenComparing(Person::getAge)).map(Person::getName)
                .collect(Collectors.toList());
        // 先按工资再按年龄自定义排序（降序）
        List<String> newList4 = personList.stream().sorted((p1, p2) -> {
            if (p1.getSalary() == p2.getSalary()) {
                return p2.getAge() - p1.getAge();
            } else {
                return p2.getSalary() - p1.getSalary();
            }
        }).map(Person::getName).collect(Collectors.toList());

        System.out.println("按工资升序排序：" + newList);
        System.out.println("按工资降序排序：" + newList2);
        System.out.println("先按工资再按年龄升序排序：" + newList3);
        System.out.println("先按工资再按年龄自定义降序排序：" + newList4);
    }

    /**
     * 提取/组合
     *  concat : 合并
     *  distinct : 去重
     *  skip : 跳过前几个元素
     *  limit : 限定执行次数
     */
    static void test14(){
        System.out.println("personList 链表元素个数：" + personList.size());
        System.out.println("concat两个personList，并不去重，元素个数：" + Stream.concat(personList.stream(), personList.stream()).count());
        System.out.println("concat两个personList，并去重，元素个数：  " + Stream.concat(personList.stream(), personList.stream()).distinct().count());
        System.out.println("skip 前2个元素 ，元素个数：  " + personList.stream().skip(2).count());
        System.out.println("limit 限制到第4个位置 ，元素个数：  " + personList.stream().limit(4).count());
    }
    static class Person{
        private String name;  // 姓名
        private int salary; // 薪资
        private int age; // 年龄
        private String sex; //性别
        private String area;  // 地区

        // 构造方法
        public Person(String name, int salary, int age,String sex,String area) {
            this.name = name;
            this.salary = salary;
            this.age = age;
            this.sex = sex;
            this.area = area;
        }

        @Override
        public String toString() {
            return "Person{" +
                    "name='" + name + '\'' +
                    ", salary=" + salary +
                    ", age=" + age +
                    ", sex='" + sex + '\'' +
                    ", area='" + area + '\'' +
                    '}';
        }

        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }

        public int getSalary() {
            return salary;
        }

        public void setSalary(int salary) {
            this.salary = salary;
        }

        public int getAge() {
            return age;
        }

        public void setAge(int age) {
            this.age = age;
        }

        public String getSex() {
            return sex;
        }

        public void setSex(String sex) {
            this.sex = sex;
        }

        public String getArea() {
            return area;
        }

        public void setArea(String area) {
            this.area = area;
        }
    }
}